package com.koron.common.core.aop;

import com.alibaba.fastjson.JSONObject;
import com.koron.bean.system.staff.Constant;
import com.koron.bean.system.staff.vo.EamUser;
import com.koron.bean.util.ServletUtils;
import com.koron.common.core.business.log.entity.po.LogOperatePo;
import com.koron.common.core.business.log.util.redisImpl.OperateLogRedisPublisher;
import com.koron.common.core.config.ThreadLocalContext;
import com.koron.common.core.filter.RequestWrapper;
import com.koron.common.core.util.CodeTools;
import com.koron.common.core.util.IpUtil;
import com.koron.common.core.util.StringUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @ClassName SystemLogAspect
 * @description
 * @Author zhouj
 * @Date 2022/6/8 11:40
 */
@Slf4j
@Aspect
@Component
public class SystemLogAspect {

    @Autowired
    OperateLogRedisPublisher operateLogRedisPublisher;

    @Value("${spring.application.name}")
    private String applicationName;

    @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
    public void logPointCut() {
    }

    @Before("logPointCut()")
    public void beforeRequest() {
    }

    @Around("logPointCut()")
    public Object run2(ProceedingJoinPoint joinPoint) throws Throwable {

        Date inTime = new Date();
        Object result = null;
        String message = null;
        try {
            result = joinPoint.proceed();
        } catch (Exception e) {
            message = e.getMessage();
            Throwable ex = e;
            while (StringUtils.isEmpty(message) && ex != null) {
                if(ex.getCause()==null){
                    break;
                }
                ex = ex.getCause();
                message = ex.getMessage();
            }
            throw e;
        } finally {
            try {
                saveLog(joinPoint, result, inTime, message);
            } catch (Exception e) {
                log.error("记录日志时发生错误:{}", e);
            }
        }
        //如果这里不返回result，则目标对象实际返回值会被置为null
        return result;
    }

    public void saveLog(JoinPoint joinPoint, Object result, Date inTime, String exceptionMessage) {

        //1.拿到当前请求
        ServletRequestAttributes requestAttributes = ServletUtils.getRequestAttributes();
        if (requestAttributes == null) {
            return;
        }
        HttpServletRequest request = requestAttributes.getRequest();
        if (request == null) {
            return;
        }
        HttpServletResponse response = requestAttributes.getResponse();

        //2.从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //3.拿到请求头参数Map
        Map<String, String> headers = new HashMap<>();
        String name;
        request.getAttributeNames();
        for (Enumeration e = request.getHeaderNames(); e.hasMoreElements(); ) {
            name = e.nextElement().toString();
            headers.put(name, request.getHeader(name));
        }

        //4.请求param参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        //5.请求体内容
        String requestBoby = null;
        //6.请求方法是非查询 则需要将boby拿出来记录下
        String requestMethod = request.getMethod();
        //6.1 GET方法请求不记录,直接返回
        if (HttpMethod.GET.matches(requestMethod)) {
            return;
        }
        //6.2 只记录非查询访问日志,并且ContentType为可记录类型
        if ((HttpMethod.POST.matches(requestMethod) || HttpMethod.PUT.matches(requestMethod)) && request instanceof RequestWrapper) {
            String value = request.getContentType();
            //付工报表来的 特殊处理一下
            if ("content=text/html;charset=ANSI".equals(value)) {
                value = MediaType.TEXT_HTML_VALUE;
            }
            MediaType contentType = org.springframework.util.StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null;
            if (shouldRecordBody(contentType)) {
                requestBoby = getBodyString((RequestWrapper) request);
            }
        }

        //7.构建日志记录对象,由于日志记录了 入参,出参 最好只保留一定时限的记录
        LogOperatePo logOperatePo = new LogOperatePo();
        //7.1 获取操作
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        //7.2 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //7.3 获取请求的方法名
        String methodName = method.getName();
        if (apiOperation != null) {
            logOperatePo.setMethodName(apiOperation.value());
        }

        //7.4 获取本机ip
        String hostAddress = getHostIp();
        logOperatePo.setId(CodeTools.getCode32());
        logOperatePo.setAccount(ThreadLocalContext.get().getAccount());
        logOperatePo.setIp(IpUtil.getIpAddr(ServletUtils.getRequest()));
        try {
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(result));
            logOperatePo.setStatus(Objects.isNull(result) ? HttpStatus.INTERNAL_SERVER_ERROR.value() + "" : jsonObject.getIntValue("code") + "");
        } catch (Exception e) {
            log.error("记录日志json解析失败:{}", e.getMessage());
            logOperatePo.setStatus(HttpStatus.OK.value() + "");
        }
        logOperatePo.setInTime(inTime);
        logOperatePo.setUseTime(new Double(System.currentTimeMillis() - inTime.getTime()));
        logOperatePo.setMethodPath(className + "." + methodName);
        logOperatePo.setType(ServletUtils.getRequest().getMethod());
        logOperatePo.setRequestUrl(request.getRequestURL().toString());
        logOperatePo.setRequestParam(MapUtils.isEmpty(parameterMap) ? null : JSONObject.toJSONString(parameterMap));
        //7.5 GET请求返回的数据很多  所以不记录get请求的返回信息
        logOperatePo.setResponse(Objects.isNull(result) || HttpMethod.GET.matches(requestMethod) ? null : JSONObject.toJSONString(result));
        logOperatePo.setRequestBoby(requestBoby == null ? null : JSONObject.toJSONString(requestBoby));
        logOperatePo.setApplicationName(applicationName);
        if (ThreadLocalContext.getApp() != null) {
            logOperatePo.setAppid(ThreadLocalContext.getApp().getId());
        }
        logOperatePo.setRequestHeader(JSONObject.toJSONString(headers));
        logOperatePo.setExMessage(exceptionMessage);
        logOperatePo.setLocalIp(hostAddress);
        logOperatePo.setAppid(ThreadLocalContext.get().getCurrDs());

        //7.6 如果是代理人操作的,顺便记录一下代理人账号
        EamUser eamUser = ThreadLocalContext.get();
        EamUser agent = eamUser.getAgent();
        if (!Objects.isNull(agent)) {
            logOperatePo.setAgent(agent.getAccount());
        }
        operateLogRedisPublisher.publish(Constant.EAM_LOG, logOperatePo);
    }

    /*
     * 功能描述 获取请求体参数
     * @author zhouj
     * @date 2022/6/16 16:29
     * @param
     * @return
     */
    private String getBodyString(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(),e);
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error(e.getMessage(),e);
                }
            }
        }
        return sb.toString().trim();
    }

    /*
     * 功能描述 获取主机ip
     * @author zhouj
     * @date 2022/6/16 16:30
     * @param
     * @return
     */
    private static String getHostIp() {
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress ip = (InetAddress) addresses.nextElement();
                    if (ip != null
                            && ip instanceof Inet4Address
                            && !ip.isLoopbackAddress() //loopback地址即本机地址，IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
                            && ip.getHostAddress().indexOf(":") == -1) {
                        return ip.getHostAddress();
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
        return null;
    }

    /*
     * 功能描述 是否需要记录boby
     * @author zhouj
     * @date 2022/6/16 16:30
     * @param
     * @return
     */
    private boolean shouldRecordBody(MediaType contentType) {
        if (contentType == null) {
            return true;
        }
        String type = contentType.getType();
        String subType = contentType.getSubtype();

        if ("application".equals(type)) {
            return "json".equals(subType) || "x-www-form-urlencoded".equals(subType) || "xml".equals(subType) || "atom+xml".equals(subType) || "rss+xml".equals(subType);
        } else if ("text".equals(type)) {
            return true;
        }
        //暂时不记录form
        return false;
    }

}
